package com.aratana.util;

import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtilities {

	@SuppressWarnings("unchecked")
	public static <T> List<Class<? extends T>> getClasses(final String jarPath, final String packageName, final Class<T> instanceOfClass) throws Exception {
		final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		final String path = packageName == null ? "" : packageName.replace('.', '/');
		final List<File> files = new ArrayList<File>();
		if (jarPath != null && !jarPath.trim().isEmpty()) {
			files.add(new File(jarPath));
		} else {
			files.add(new File(Class.forName(Thread.currentThread().getStackTrace()[Thread.currentThread().getStackTrace().length - 1].getClassName()).getProtectionDomain().getCodeSource().getLocation().toURI()));
		}
		final List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
		for (final File file : files) {
			if (file.exists()) {
				if (file.isFile()) {
					final JarFile jar = new JarFile(file);
					final Enumeration<JarEntry> entries = jar.entries();
					boolean found = false;
					while (entries.hasMoreElements()) {
						final JarEntry entry = entries.nextElement();
						if (entry.getName().indexOf(path) == 0) {
							if (!entry.isDirectory()) {
								found = true;
								if (entry.getName().indexOf('$') == -1 && entry.getName().lastIndexOf(".class") == entry.getName().length() - 6) {
									try {
										final Class<?> clazz = Class.forName(entry.getName().substring(0, entry.getName().length() - 6).replace('/', '.'), false, classLoader);
										if (instanceOfClass.isAssignableFrom(clazz) && !clazz.isInterface()) {
											classes.add((Class<? extends T>) clazz);
										}
									} catch (final Throwable e) {
										System.err.println(e.getLocalizedMessage());
									}
								}
							}
						} else if (found) {
							break;
						}
					}
				} else {
					classes.addAll(getDirClasses(file.getAbsolutePath().length() + 1, file, path.replace('/', '\\'), instanceOfClass, classLoader));
				}
			}
		}
		return classes;
	}

	@SuppressWarnings("unchecked")
	private static <T> List<Class<? extends T>> getDirClasses(final int originalLength, final File parent, final String path, final Class<T> instanceOfClass, final ClassLoader classLoader) throws Exception {
		final List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
		for (final File f : parent.listFiles()) {
			if (!f.isDirectory() && f.getAbsolutePath().substring(originalLength).indexOf(path) == 0) {
				if (f.getName().indexOf('$') == -1 && f.getName().lastIndexOf(".class") == f.getName().length() - 6) {
					final Class<?> clazz = Class.forName(f.getAbsolutePath().substring(originalLength, f.getAbsolutePath().length() - 6).replace('\\', '.'), false, classLoader);
					if (instanceOfClass.isAssignableFrom(clazz) && !clazz.isInterface()) {
						classes.add((Class<? extends T>) clazz);
					}
				}
			} else if (f.isDirectory()) {
				if (!path.isEmpty()) {
					if (f.getAbsolutePath().substring(originalLength).indexOf(path) == 0 || path.indexOf(f.getAbsolutePath().substring(originalLength)) == 0) {
						classes.addAll(getDirClasses(originalLength, f, path, instanceOfClass, classLoader));
					}
				} else {
					classes.addAll(getDirClasses(originalLength, f, path, instanceOfClass, classLoader));
				}
			}
		}

		return classes;
	}

	private ClassUtilities() {
	}
}
